#include "SymbolHistClient.h"
#include <boost/bind.hpp>
#include <boost/date_time.hpp>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <stdexcept>
#include <fstream>

namespace
{
    /**
     * Default column separator to be used while writing data to the
     * output file if user did not specify one.
     */
    const std::string DEFAULT_COLUMN_SEPARATOR = "," ;
}

namespace OtfeedExamples
{
    class SymbolHistClient::Implementation : public OTClient
    {
    public:
        //! Default constructor.
        Implementation() ;
        
        //! Destructor. 
        ~Implementation() { /* empty */ }

        //! Implementation of SymbolHistClient::GetHistData()
        void
        GetHistData( const SymbolHistClient::DataRequest& dataRequest ) ;
    private:
        /**
         * Return true if a list of exchanges has been received by the client.
         */
        bool
        IsExchangeListReceived() const ;

        /**
         * Return true if the entire symbol list for the specified exchange 
         * has been received from the opentick server(s).
         */
        bool
        IsSymbolListReceived() const ;

        /**
         * Return true if an error was encountered while communicating with
         * the otFeed servers.
         */
        bool
        IsErrorEncountered() const ;

        /**
         * Return true if attempt to login to the OpenTick servers was
         * successful.
         */
        bool
        IsLoginComplete() const ;

        /**
         * Return true if all requested historical data has been received.
         */
        bool
        IsHistoricalDataReceived() const ;

        //
        // Overloaded OTClient methods. These are declared private here to
        // make sure that users of SymbolHistClient only use the customized
        // API.
        //
        virtual void onLogin() ;

        virtual void onError( OTError* otError ) ;

        virtual void onMessage( OTMessage* otMessage ) ;

        virtual void onHistQuote( OTQuote* otQuote ) ;
        
        //! Flag that indicates if login to the OpenTick server(s) was successful
        bool mIsLoginSuccessful ;

        /**
         *  Flag that indicates if all requested historical data has
         *  been received from the server.
         */
        bool mIsHistDataReceived ;
        
        /**
         * The request that is being processed by the client.
         * 
         * :TODO: This is not thread safe. Modify the implementation so that
         * a single client can be used to process requests from multiple 
         * threads.
         */
        DataRequest mCurrRequest ;

        /**
         * Output file stream instance that will be used to write tick data
         * to the user-specified output file. 
         */
        std::ofstream mOutputStream ;
        
        /**
         * Flag that indicates if an error has been encoutered while communicating
         * with the otFeed server.
         */
        bool mIsErrorEncountered ;
        
        /**
         * Identifier for request to obtain historical data.
         */
        int mHistDataRequestId ;
    } ;

    SymbolHistClient::SymbolHistClient() :
            mImpl( new SymbolHistClient::Implementation )
    {
        /* empty */
        return ;
    }
    
    SymbolHistClient::~SymbolHistClient()
    {
    	/* empty */
    	return ; 
    }

    void
    SymbolHistClient::GetHistData( const DataRequest& dataRequest )
    {
        mImpl->GetHistData( dataRequest ) ;
        return ;
    }
    
    inline
    SymbolHistClient::Implementation::Implementation() :
        mIsLoginSuccessful( false ),
        mIsHistDataReceived( false ),
        mIsErrorEncountered( false ), 
        mHistDataRequestId( -1 ) 
    {
        /* empty */
    }

    inline
    void
    SymbolHistClient::Implementation::GetHistData( const DataRequest& dataRequest )
    {
        //
        // Save the current request. 
        //
        this->mCurrRequest = dataRequest ;
        //
        // If the user did not specify a column separator then use the default 
        // column separator. 
        // 
        // :TODO: Write a default constructor for DataRequest that
        // initializes all its members with default values so we do
        // not have to perform such checks.
        //
        if ( 0 == mCurrRequest.mColumnSeparator.length() )
        {
            //
            // Display a warning if verbosity is enabled. 
            //
            if ( mCurrRequest.mIsVerbose )
            {
                std::ostringstream warning_message ;
                warning_message << __FILE__ << ":" << __LINE__ << ": "
                                << "WARNING: No (or empty) column separator specified."
                                << std::endl 
                                << "Using default column separator '"
                                << DEFAULT_COLUMN_SEPARATOR
                                << "' instead."
                                << std::endl ;
                std::clog << warning_message.str() ;
            }
            mCurrRequest.mColumnSeparator = DEFAULT_COLUMN_SEPARATOR ;
        }
        //
        // Make sure that we can open the specified output file for writing. 
        //
        this->mOutputStream.open( this->mCurrRequest.mOutputFileName.c_str() ) ;
        if ( ! this->mOutputStream )
        {
            std::ostringstream error_message ;
            error_message << __FILE__ << ":" << __LINE__ << ": "
                          << "ERROR: Unable to open file " 
                          << this->mCurrRequest.mOutputFileName
                          << " for writing."
                          << std::endl ;
            throw std::runtime_error( error_message.str() ) ;
        }
        //
        // Convert the user-specified times into the format expected by OpenTick library.
        // See http://www.opentick.com/dokuwiki/doku.php?id=general:date_time_issues
        //
        int start_time = 0 ;
        int stop_time = 0 ;
        try
        {
            using namespace boost::gregorian ;
            using namespace boost::posix_time ;
            using namespace boost::local_time ;
            //
            // Parse the start and stop times into ptime instances.
            //
            ptime start_ptime = from_iso_string( mCurrRequest.mFromDate ) ;
            ptime stop_ptime  = from_iso_string( mCurrRequest.mToDate ) ;
            //
            // OpenTick servers use number of seconds since January 1, 1970
            // as the timestamps for all dates. All times are in GMT.
            //
            ptime epoch_ptime( date( 1970, 1, 1 ) ) ;
            //
            // Calculate the number seconds since epoch for the start and stop
            // times specified by the user.
            //
            time_duration time_diff = ( start_ptime - epoch_ptime ) ;
            start_time = time_diff.total_seconds() ;
            //
            time_diff = ( stop_ptime - epoch_ptime ) ;
            stop_time = time_diff.total_seconds() ;
        }
        catch( const std::exception& stdException )
        {
            std::ostringstream error_message ;
            error_message << std::endl
                          << __FILE__ << ":" << __LINE__ << ": "
                          << "ERROR: Could not parse the specified dates."
                          << std::endl
                          << stdException.what()
                          << std::endl ;
            std::cerr << error_message.str() ;
            return ;
        }
        // -----------------------------------------------------------------
        //
        // Use the specified OpenTick server to get data. 
        //
        // :KLUDGE: The const_cast<> is necessary below because
        // OTClient::addHost requires a 'char*' argument even though
        // it should really be a 'const char*' or a 'const
        // std::string'. Hopefully this will change in future version
        // of otFeed API and we will not need the const_cast<> at that
        // point.
        //
        assert( 0 != mCurrRequest.mOpenTickServer.length() ) ;
        this->addHost( const_cast<char*>( mCurrRequest.mOpenTickServer.c_str() ), 
                       mCurrRequest.mOpenTickServerPort ) ;
        //
        // Login to the server and trigger the retrieval of historical
        // data.
        //
        // :KLUDGE: OTClient::login() takes two 'char*' arguments in the
        // current version of OTFeed. However, it should really take
        // 'const char*' arguments. In order to use the current version of
        // OTClient::login() we have to use a const_cast<> below. Hope
        // that this will be fixed in future versions.
        //
        if ( mCurrRequest.mIsVerbose )
        {
            std::clog << __FILE__ << ":" << __LINE__ << ": "
                      << "Logging on to opentick server "
                      << mCurrRequest.mOpenTickServer << ":" 
                      << mCurrRequest.mOpenTickServerPort
                      << " as user " 
                      << mCurrRequest.mAccountLogin
                      << std::endl ;
        }
        this->login( const_cast<char*>( mCurrRequest.mAccountLogin.c_str() ), 
                     const_cast<char*>( mCurrRequest.mAccountPassword.c_str() ) ) ;

        //
        // Wait for login to succeed.
        //
        while( ! ( this->IsLoginComplete() ||
                   this->IsErrorEncountered() ) )
        {
            sleep(1) ;
        }
        //
        if ( this->IsLoginComplete() && mCurrRequest.mIsVerbose )
        {
            std::clog << __FILE__ << ":" << __LINE__ << ": "
                      << "Sucessfully logged on to opentick server."
                      << std::endl ;
        }
        else if ( this->IsErrorEncountered() )
        {
            std::ostringstream error_message ;
            error_message << __FILE__ << ":" << __LINE__ << ": "
                          << "Failed to login to opentick server."
                          << std::endl ;
            throw std::runtime_error( error_message.str().c_str() ) ;
        }
        //
        // Request historical data. 
        //
        std::auto_ptr<OTDataEntity> data_entity( 
            new OTDataEntity ( mCurrRequest.mExchangeCode.c_str(),
                               mCurrRequest.mSymbol.c_str() ) ) ;

        if ( mCurrRequest.mIsVerbose )
        {
            std::clog << __FILE__ << ":" << __LINE__ << ": "
                      << "Requesting historical data for symbol "
                      << mCurrRequest.mSymbol
                      << " from exchange "
                      << mCurrRequest.mExchangeCode
                      << " for the duration from "
                      << mCurrRequest.mFromDate 
                      << "(" << start_time << ")"
                      << " to "
                      << mCurrRequest.mToDate 
                      << "(" << stop_time << ")"
                      << std::endl ;
        }
        
        mHistDataRequestId = 
            this->requestHistData( data_entity.get(), 
                                   start_time, 
                                   stop_time,
                                   OT_HIST_RAW_TICKS ) ;
        while( ! ( this->IsHistoricalDataReceived() ||
                   this->IsErrorEncountered() ) )
        {
            sleep(1) ;
        }
        //
        if ( this->IsHistoricalDataReceived() && mCurrRequest.mIsVerbose )
        {
            std::clog << __FILE__ << ":" << __LINE__ << ": "
                      << "Sucessfully received historical data from opentick server."
                      << std::endl ;
        }
        else if ( this->IsErrorEncountered() )
        {
            std::ostringstream error_message ;
            error_message << __FILE__ << ":" << __LINE__ << ": "
                          << "Failed to receive historical data from opentick server."
                          << std::endl ;
            throw std::runtime_error( error_message.str().c_str() ) ;
        }
        return ;
    }


    inline
    bool
    SymbolHistClient::Implementation::IsErrorEncountered() const
    {
        return mIsErrorEncountered ;
    }


    inline
    bool
    SymbolHistClient::Implementation::IsLoginComplete() const
    {
        return mIsLoginSuccessful ;
    }

    inline
    bool
    SymbolHistClient::Implementation::IsHistoricalDataReceived() const
    {
        return mIsHistDataReceived ;
    }
    
    inline
    void
    SymbolHistClient::Implementation::onLogin()
    {
        mIsLoginSuccessful = true ;
        return ;
    }

    inline
    void
    SymbolHistClient::Implementation::onError( OTError* const otError )
    {
        //
        // Display a message indicating the error type and description.
        //
        std::ostringstream error_message ;
        error_message << __FILE__ << ":" << __LINE__ << ": "
                      << "ERROR: [" << otError->getRequestId()
                      << ":" << otError->getCode()
                      << "] "
                      << otError->getDescription()
                      << "."
                      << std::endl ;
        std::cerr << error_message.str() ;
        //
        // Set a flag indicating that processing has stopped due to an
        // error. Unfortunately, one cannot throw an exception here because
        // of the way otfeed library is implemented; it catches all exceptions
        // through by Client::onError() and simply ignores them. This will
        // hopefully be fixed in future.
        //
        this->mIsErrorEncountered = true ;
        return ;
    }

    inline
    void
    SymbolHistClient::Implementation::onMessage( OTMessage* otMessage )
    {
        //
        // :TRICKY: When a list of symbols is requested from the server(s),
        // the onListSymbols() method is called multiple times with a partial
        // list. When the entire list has been transmitted by the server, the
        // onMessage() method is called with message code of OT_LIST_SYMBOLS.
        // We use this message to indicate that the entire list has been
        // received.
        //
        if ( ( 0 != otMessage ) &&
             ( otMessage->getRequestId() == mHistDataRequestId ) &&
             ( OT_LIST_SYMBOLS == otMessage->getCode() ) )
        {
            //
            // The entire list of symbols has been received.
            //
            this->mIsHistDataReceived = true ;
        }
        else if ( 0 != otMessage )
        {
            std::cerr << __FILE__ << ":" << __LINE__ << ": "
                      << "Received unexpected message with code" 
                      << otMessage->getCode() 
                      << std::endl ;
        }
        return ;
    }


    /**
     * Utility function to write data contained in the specified
     * OTQuote instance to the specified output stream. The data is
     * written to a single line with individual fields separated by
     * columnSeparator. Of course, the columnSeparator itself may
     * contain newline character(s) resulting in multiple lines of
     * output.
     * 
     * @param outputStream Output stream to which the data will be written. 
     * 
     * @param otQuote Reference to the OTQuote instance that will be
     * dumped to the output stream.
     * 
     * @param columnSeparator This string will be used to separate
     * individual fields of the specified OTQuote instance.
     * 
     */
    inline
    void
    write_quote_with_separateor( std::ostream&     outputStream, 
                                 OTQuote&          otQuote,
                                 const std::string columnSeparator )
    {
        //
        // :NOTE: Calls to OTQuote::getAskExchange(),
        // OTQuote::getBidExchange(), OTQuote::getExchange(), and
        // OTQuote::getSymbol() appear to hang for some reason. Hence,
        // data corresponding to these calls will not be written to
        // the output stream until this has been fixed.
        // 
        outputStream << otQuote.getTimestamp()     << columnSeparator 
                     << otQuote.getAskSize()       << columnSeparator
                     << otQuote.getBidSize()       << columnSeparator
                     << otQuote.getIndicator()     << columnSeparator
                     << otQuote.getTickIndicator() << columnSeparator
                     << otQuote.getAskPrice()      << columnSeparator 
                     << otQuote.getBidPrice()      << columnSeparator
                     << boost::posix_time::from_time_t( otQuote.getTimestamp() )
                     << std::endl ; 
        return ;
    }

    inline
    void
    SymbolHistClient::Implementation::onHistQuote( OTQuote* otQuote )
    {
        if ( 0 == otQuote )
        {
            //
            // :TODO: Throw an exception here instead of just displaying an 
            // error message.
            //
            std::cerr << __FILE__ << ":" << __LINE__ << ": "
                      << "NULL OTQuote object received."
                      << std::endl ;
            return ;
        }
        
        if ( ! this->mOutputStream )
        {
            std::ostringstream error_message ;
            error_message << __FILE__ << ":" << __LINE__ << ": "
                          << "ERROR: Unable to write tick data to output file."
                          << std::endl ;
            throw std::runtime_error( error_message.str() ) ;
        }

        write_quote_with_separateor( this->mOutputStream,
                                     *otQuote, 
                                     mCurrRequest.mColumnSeparator ) ;

        if ( this->mCurrRequest.mIsVerbose )
        {
            write_quote_with_separateor( std::clog, 
                                         *otQuote, 
                                         " " ) ;
        }

        return ;
    }
}

namespace
    {}
